#!BPY
# encoding: utf-8

"""
Name: 'KISS CM3D model (.anz)'
Blender: 249
Group: 'Export'
Tooltip: 'CM3D .anz exporter'
"""

__author__ = 'Desuwa'
__version__ = '0.4.0'
__url__ = "http://code.google.com/p/anz2blend/"
__email__ = "desuwane@gmail.com"

"""
The MIT License

Copyright (c) 2011 Desuwa

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""

import Blender
import BPyMessages
import bpy
import os
from decimal import Decimal
from Blender import BGL, Ipo
from Blender.BGL import *
import subprocess
from struct import pack, unpack

Vector = Blender.Mathutils.Vector
Matrix = Blender.Mathutils.Matrix
Euler = Blender.Mathutils.Euler
Quaternion = Blender.Mathutils.Quaternion
ScaleMatrix = Blender.Mathutils.ScaleMatrix
RotationMatrix = Blender.Mathutils.RotationMatrix
TranslationMatrix = Blender.Mathutils.TranslationMatrix

# NvTriStrip binary location
# Defaults to Blender's scripts directory
NvTriStripPath = Blender.sys.join(Blender.Get("scriptsdir"), "NvTriStripper-cli.exe")

# Check the NvTriStrip binary
if not Blender.sys.exists(NvTriStripPath):
	raise Exception("Can't find NvTriStrip binary (%s)" % NvTriStripPath)

#----------------
# BinaryReader
#----------------
def readSingle(r):
	data = r.read(4)
	return float(unpack("<f", data)[0]) if data != "" else False

def readInt(r):
	data = r.read(4)
	return int(unpack("<i", data)[0]) if data != "" else False

def readString(r):
	l = readInt(r)
	if l == "": return False
	data = r.read(l)
	return "".join(unpack("<%ds" % l, data)) if data != "" else False

#----------------
# BinaryWriter
#----------------
def writeSingle(f, w):
	data = pack("<f", f)
	w.write(data)

def writeInt(i, w):
	data = pack("<i", i)
	w.write(data)

def writeString(s, w):
	l = len(s)
	data = pack("<i%ds" % l, l, s)
	w.write(data)
	
def writeByte(b, w):
	data = pack("<B", b)
	w.write(data)

def writeMatrix(m, w):
	for row in m:
		writeSingleArray(row, w)

def writeSingleArray(a, w):
	for f in a:
		w.write(pack("<f", f))
		
def writeByteArray(a, w):
	for b in a:
		w.write(pack("<B", b))

def writeKeyframes(keyframes, w):
	kfCount = len(keyframes)
	# X
	writeByte(1, w)
	writeSingle(0.0, w)
	writeInt(kfCount, w)
	for kf in keyframes:
		writeSingle(kf["num"], w) # frame number
		writeSingle(kf["val"].x, w) # value
		writeSingle(kf["tan"], w) # tangent
	# Y
	writeByte(1, w)
	writeSingle(0.0, w)
	writeInt(kfCount, w)
	for kf in keyframes:
		writeSingle(kf["num"], w)
		writeSingle(kf["val"].y, w)
		writeSingle(kf["tan"], w)
	# Z
	writeByte(1, w)
	writeSingle(0.0, w)
	writeInt(kfCount, w)
	for kf in keyframes:
		writeSingle(kf["num"], w)
		writeSingle(kf["val"].z, w)
		writeSingle(kf["tan"], w)

def writeStaticFrame(value, w):
		writeByte(0, w)
		writeSingle(value.x, w)
		writeInt(0, w)
		writeByte(0, w)
		writeSingle(value.y, w)
		writeInt(0, w)
		writeByte(0, w)
		writeSingle(value.z, w)
		writeInt(0, w)

#----------------
# Anz
#----------------
MATERIAL_ANIM_FLAG = 2
MATNAME_MIN_LEN = 8

class AnzBone():
	def __init__(self, bid = -1):
		self.bid = bid
		self.name = ""
		self.p = None # parent
		self.c = None # child
		self.pnc = None # parent's next child
		self.ppc = None # parent's previous child
		self.children = []
		self.cnum = 0 # child count
		self.ctag = 0 # last tagged child
		self.scale = 1
		self.matrix = [] # output matrixes
		self.asMat = None # armature space matrix
		self.asMatUs = None # unscaled AS matrix
		self.psMat = None # parent space matrix
	
	def __repr__(self):
		return self.__str__()
	
	def __str__(self):
		return "[%d:%s]=[%s:%d] [%s:%d] [%s:%d] [%s:%d]" %\
		(self.bid, self.name, self.p.name, self.p.bid, self.c.name, self.c.bid, \
		self.ppc.name, self.ppc.bid, self.pnc.name, self.pnc.bid)

class AnzVertex():
	x = y = z = nx = ny = nz = tu = tv = 0
	bones = []
	weights = []
	m = 0
	
	def __repr__(self):
		return self.__str__()
	
	def __str__(self):
		return "(%.3f %.3f %.3f | %.3f %.3f %.3f | %.3f %.3f)" %\
		(self.x, self.y, self.z, self.nx, self.ny, self.nz, self.tu, self.tv)
	
	def __eq__(self, v):
		return self.x == v.x and self.y == v.y and self.z == v.z \
		and self.nx == v.nx and self.ny == v.ny and self.nz == v.nz \
		and self.tu == v.tu and self.tv == v.tv \
		and self.bones == v.bones and self.weights == v.weights
	
	def __hash__(self):
		h = hash(self.x) ^ hash(self.y) ^ hash(self.z)\
		^ hash(self.nx) ^ hash(self.ny) ^ hash(self.nz)\
		^ hash(self.tu) ^ hash(self.tv)
		for b in self.bones:
			h ^= hash(b)
		for w in self.weights:
			h ^= hash(w)
		return h

def prepareScene():
	data = {}
	data["meshes"] = []
	data["matToTexId"] = {}
	data["materials"] = []
	data["matAnims"] = []
	data["armature"] = None
	data["actions"] = {}
	data["morphchannels"] = 0
	
	# Checking meshes
	index = 0
	for obj in bpy.data.scenes.active.objects:
		if obj.getType() != "Mesh":
			continue
		# Skip if object is not renderable
		if ONLYRENDERABLE and obj.restrictRender == True:
			print "%s : mesh is not renderable, skipping." % obj.name
			continue
		# Checking if object is a morph target
		if obj.name.startswith("m-"):
			chn = int(obj.name.split("-")[1]) + 1
			if chn > data["morphchannels"]:
				data["morphchannels"] = chn
			continue
		# Checking if the mesh has a material/texture/image/uv layer
		mesh = obj.getData(mesh = True)
		if mesh.materials[0] is None:
			raise Exception("%s : mesh doesn't have a material at index 1. Aborting." % obj.name)
		if mesh.materials[0].textures[0] is None:
			raise Exception("%s : material doesn't have a texture. Aborting." % obj.name)
		image = mesh.materials[0].textures[0].tex.image
		if image is None:
			raise Exception("%s : texture doesn't have an image. Aborting." % obj.name)
		if mesh.activeUVLayer is None:
			raise Exception("%s : mesh doesn't have an UV layer. Aborting." % obj.name)
		
		texsize = mesh.materials[0].textures[0].size
		texfile = Blender.sys.expandpath(image.getFilename())
		if not data["matToTexId"].has_key(texfile):
			if not (texfile.endswith(".png") or texfile.endswith(".dds")):
				raise Exception("Texture must be a PNG or a DDS file (%s)" % texfile)		
			if not Blender.sys.exists(texfile):
				raise Exception("Texture file does not exist: " + texfile)
			data["matToTexId"][texfile] = len(data["materials"])
			# Display mode:
			# 1 = double sided, 2 = inverted, 3 = unchanged
			matName = mesh.materials[0].name
			if matName.startswith("d-"):
				matMode = 1
			elif matName.startswith("i-"):
				matMode = 2
			else:
				matMode = 3
			texname = Blender.sys.basename(texfile)
			if len(texname) < MATNAME_MIN_LEN:
				raise Exception("Texture file name must be at least " \
				+ "%d characters long, including the extension: (%s)" \
				% (MATNAME_MIN_LEN, texname))
			data["materials"].append( { "mode": matMode,
				"texfile": texfile,
				"texname": texname,
				"texsize": (texsize[0], texsize[1]) } )
		m = {}
		m["matId"] = data["matToTexId"][texfile]
		m["objName"] = obj.name
		m["isVisible"] = obj.restrictDisplay
		data["meshes"].append(m)
		
		# Checking for Material animations
		matIpo = mesh.materials[0].getIpo()
		if matIpo is not None:
			anim = {"x": [], "y": []}
			curveX = matIpo[Ipo.MA_OFSX]
			if curveX is not None:
				if len(curveX.bezierPoints) > 0:
					anim["x"] = curveX.bezierPoints
			curveY = matIpo[Ipo.MA_OFSY]
			if curveY is not None:
				if len(curveX.bezierPoints) > 0:
					anim["y"] = curveY.bezierPoints
			anim["matId"] = m["matId"]
			if anim["x"] or anim["y"]:
				data["matAnims"].append(anim)
		index += 1
		
	if index == 0:
		raise Exception("Nothing to export.")
	
	if MATANIM and not data["matAnims"]:
		raise Exception("No material animation to export. Aborting.")
	
	# Checking armature
	if EXPARM or BONEANIM:
		for obj in bpy.data.scenes.active.objects:
			if obj.type == "Armature":
				if obj.restrictRender:
					print obj.name + " : armature is not renderable, skipping"
					continue
				else:
					data["armature"] = obj
					break
		if data["armature"] is None:
			raise Exception("Can't find any armature to export. Aborting.")
	
	# Checking bones animation
	if BONEANIM:
		for action in bpy.data.actions:
			if action.users == 0:
				continue
			if action.name.count(".") > 1:
				raise Exception("Invalid name for action %s: too many '.' separators" % action.name)
			actId = action.name.split(".")
			# actions must have the armature name as prefix
			if actId[0] != data["armature"].name:
				continue
			if len(actId) > 1:
				if actId[-1].lower() == "dummy":
					data["actions"]["dummy"] = action
				else:
					data["actions"][int(actId[-1])] = action
			else:
				data["actions"][0] = action
		if not data["actions"]:
			raise Exception("Can't find any actions to export. Aborting.")
	
	return data

def writeMatAnims(animations, w):
	animCount = len(animations)
	if animCount == 0:
		print "No material animations to export"
		return
	print "Writing material animations"
	endFrame = bpy.data.scenes.active.getRenderingContext().eFrame
	print "\tLength: %s frame(s)" % endFrame
	writeInt(1, w) # animation block count
	writeInt(MATERIAL_ANIM_FLAG, w)
	writeInt(1, w) # always 1?
	writeInt(endFrame, w) # animation length
	writeInt(animCount, w) # number of materials
	for anim in animations:
		writeInt(anim["matId"], w)
		# X
		writeByte(0x01, w)
		writeInt(1601466995, w) # ascii "srt_"
		keyframes = len(anim["x"])
		writeInt(keyframes, w) # keyframe count
		for kf in xrange(0, keyframes):
			writeSingle(anim["x"][kf].pt[0] - 1, w) # kf position
			writeSingle(-anim["x"][kf].pt[1], w) # kf value
			writeSingle(0, w) # tangent
		# Y
		writeByte(0x01, w)
		writeInt(-500194116, w)
		keyframes = len(anim["y"])
		writeInt(keyframes, w)
		for kf in xrange(0, keyframes):
			writeSingle(anim["y"][kf].pt[0] - 1, w)
			writeSingle(-anim["y"][kf].pt[1], w)
			writeSingle(0, w)
	
	writeInt(255, w)

def writeMaterials(data, w):
	matcount = len(data["materials"])
	print "Writing materials"
	writeInt(matcount, w)
	for mat in data["materials"]:
		texId = data["matToTexId"][mat["texfile"]]
		writeString(mat["texname"], w) # native material name
		print "\t" + mat["texname"]
		writeSingle(mat["texsize"][0], w) # texture space scale X?
		writeSingle(mat["texsize"][1], w) # texture space scale Y?
		writeInt(0, w)
		writeInt(0, w)
		writeInt(5, w)
		writeInt(mat["mode"], w) # culling mode
		writeInt(0, w)
		writeInt(0, w)
		writeInt(texId, w)

def writeTextures(data, w):
	print "Writing textures"
	dups = {}
	for mat in data["materials"]:
		if dups.has_key(mat["texfile"]):
			writeInt(0)
			continue
		dups[mat["texfile"]] = True
		filesize = os.path.getsize(mat["texfile"])
		writeInt(filesize, w)
		with open(mat["texfile"], "rb") as r:
			w.write(r.read())

def readTextures(r):
	textureCount = readInt(r)
	for i in xrange(0, textureCount):
		readString(r)
		r.read(36)
	for i in xrange(0, textureCount):
		length = readInt(r)
		r.read(length)

def readBones(r):
	bones = []
	boneCount = readInt(r)
	for i in xrange(0, boneCount):
		bone = {}
		bone["name"] = readString(r)
		bone["parent"] = readInt(r)
		r.read(72) # params and matrixes
		r.read(64) # we only want the Armature Space one
		bone["matrix"] = Matrix(\
		[readSingle(r), readSingle(r), readSingle(r), readSingle(r)],\
		[readSingle(r), readSingle(r), readSingle(r), readSingle(r)],\
		[readSingle(r), readSingle(r), readSingle(r), readSingle(r)],\
		[readSingle(r), readSingle(r), readSingle(r), readSingle(r)],)
		r.read(256) # matrixes
		r.read(5) # flags
		bones.append(bone)
	
	return bones

def writeBones(armObj, w):
	# Very experimental.
	# Most of the checks simply try to imitate the original bone structure
	# and are probably useless
	print "Writing bones"
	arm = armObj.getData()
	
	boneCount = len(arm.bones.keys())
	
	root_bone = None
	for bone in arm.bones.values():
		if bone.parent is None:
			root_bone = bone
	
	i = 0
	anzBones = {}
	boneOrder = []
	root = AnzBone()
	root.bid = i
	root.name = root_bone.name
	root.c = AnzBone(1) if arm.bones[root.name].hasChildren() else AnzBone()
	root.p = AnzBone()
	root.ppc = AnzBone()
	root.pnc = AnzBone()
	root.children = arm.bones[root.name].children
	root.cnum = len(root.children)
	anzBones[root.name] = root
	boneOrder.append(root)
	i += 1
	
	for bone in arm.bones[root.name].getAllChildren():
		b = AnzBone()
		b.bid = i
		b.name = bone.name
		b.c = AnzBone()
		b.p = AnzBone()
		b.ppc = AnzBone()
		b.pnc = AnzBone()
		b.children = bone.children
		b.cnum = len(b.children)
		anzBones[b.name] = b
		boneOrder.append(b)
		i += 1
	
	for b in boneOrder:
		bone = arm.bones[b.name]
		if b.cnum > 0:
			b.c = anzBones[b.children[0].name]
		if bone.hasParent():
			b.p = anzBones[bone.parent.name]
			ptag = b.p.ctag - 1
			if ptag >= 0:
				b.ppc = anzBones[b.p.children[ptag].name]
			ntag = b.p.ctag + 1
			if ntag < b.p.cnum:
				b.pnc = anzBones[bone.parent.children[ntag].name]
				b.p.ctag = ntag
		
		# Matrixes
		rotMat = Euler(-90, 0, 0).toMatrix().resize4x4()
		scalePart = ScaleMatrix(1, 4)
		
		startScale = ScaleMatrix(0.8, 4)
		endScale = ScaleMatrix(1.25, 4)
		
		rotPart = bone.matrix["ARMATURESPACE"].rotationPart().resize4x4()
		transPart = TranslationMatrix(bone.matrix["ARMATURESPACE"].translationPart() * SCALEFACTOR)
		b.asMatUs = rotPart * transPart * rotMat
		
		bsMat = bone.matrix["BONESPACE"].copy().resize4x4()
		
		if bone.name == root.name:
				b.psMat = b.asMat = scalePart * b.asMatUs
		else:
			b.asMat = scalePart * b.asMatUs
			b.psMat = b.asMat * b.p.asMat.copy().invert()
		
		# Parent space
		b.matrix.append(b.psMat)
		# Armature space
		b.matrix.append(b.asMat)
		# Armature space inverse
		b.matrix.append(b.asMat.copy().invert())
		# Bone space
		b.matrix.append(bsMat)
		# Parent space rotation part
		b.matrix.append(b.asMatUs.rotationPart().resize4x4())
		# Armature space rotation part inverse
		b.matrix.append(b.asMatUs.copy().invert().rotationPart().resize4x4())
	
	# Writing
	writeInt(boneCount, w)
	tempList = [] # fixme
	for b in boneOrder:
		tempList.append({"name": b.name }) # fixme
		writeString(b.name, w)
		# structure info
		writeInt(b.p.bid, w)
		writeInt(b.c.bid, w)
		writeInt(b.ppc.bid, w)
		writeInt(b.pnc.bid, w)
		# scale
		writeSingle(b.scale, w)
		writeSingle(b.scale, w)
		writeSingle(b.scale, w)
		# rotation from parent
		rot = b.matrix[0].toEuler()
		writeSingle(rot.x, w)
		writeSingle(rot.y, w)
		writeSingle(rot.z, w)
		# translation
		trans = b.matrix[0].translationPart()
		writeSingle(trans.x, w)
		writeSingle(trans.y, w)
		writeSingle(trans.z, w)
		for i in xrange(0, 6):
			writeInt(0, w)
		# matrixes
		for i in xrange(0, 6):
			writeMatrix(b.matrix[i], w)
		# flags (incomplete, possibly useless)
		if b.name.endswith("_s") \
		or b.name.endswith("_e") \
		or b.name == root.name:
			writeByteArray([0x00, 0x00, 0x00, 0x01, 0x01], w)
		else:
			writeByteArray([0x00, 0x01, 0x01, 0x01, 0x01], w)
	
	return tempList

def writeBoneAnimation(action, armObj, bonelist, w):
	arm = armObj.getData()
	pose = armObj.getPose()
	rotMat = Euler(-90, 0, 0).toMatrix().resize4x4()
	ipolist = [ Ipo.PO_QUATW, Ipo.PO_LOCX, Ipo.PO_LOCX, Ipo.PO_LOCX ]
	
	action.setActive(armObj)
	
	writeInt(1, w) # animation type (1: bones)
	writeInt(0, w) # ?
	
	channels = action.getAllChannelIpos()
	
	# framecount
	framecount = 0
	for c in channels.values():
		for i in ipolist:
			icu = c[i]
			if icu is None: continue
			b = icu.bezierPoints[-1]
			if b.pt[0] > framecount:
				framecount = b.pt[0]
	
	writeInt(framecount, w)
	writeInt(len(bonelist), w) # bonecount
	
	i = 0
	for b in bonelist:
		if b["name"] in channels:
			channel = channels[b["name"]]
		else:
			channel = None
		
		boneRestMat = arm.bones[b["name"]].matrix["ARMATURESPACE"] * rotMat
		# root bone skip
		if i > 0:
			parentRestMat = (arm.bones[b["name"]].parent.matrix["ARMATURESPACE"] * rotMat).invert()
		else:
			parentRestMat = parentPoseMat = Matrix().identity()
		restMat = boneRestMat * parentRestMat
		
		writeString(b["name"], w)
		writeInt(i, w)
		
		# scale
		if channel is not None:
			curve = channel[Ipo.PO_SCALEX] or channel[Ipo.PO_SCALEY] or channel[Ipo.PO_SCALEZ]
		else:
			curve = None
		if curve is None:
			restScale = Vector(1, 1, 1)
			writeStaticFrame(restScale, w)
		else:
			keyframes = []
			ptmaxindex = len(curve.bezierPoints) - 1
			ptindex = 0
			for kf in curve.bezierPoints:
				armObj.evaluatePose(int(kf.pt[0]))
				bonePoseMat = pose.bones[b["name"]].poseMatrix * rotMat
				if i > 0:
					parentPoseMat = (pose.bones[b["name"]].parent.poseMatrix * rotMat).invert()
				else:
					parentPoseMat = Matrix().resize4x4().identity()
				poseScale = (bonePoseMat * parentPoseMat).scalePart()
				# TODO: fix tangent stuff
				if curve.interpolation != Blender.IpoCurve.InterpTypes.BEZIER:
					if ptindex < ptmaxindex:
						nextpt = curve.bezierPoints[ptindex + 1].pt
						tangent = (nextpt[1] - kf.pt[1]) / (nextpt[0] - kf.pt[0])
					else:
						tangent = 0.0
				else:
					tangent = 0.0
				keyframes.append( { "num": int(kf.pt[0] - 1), "val": poseScale, "tan": tangent } )
				ptindex += 1
			writeKeyframes(keyframes, w)
		
		# rotation
		restElr = restMat.toEuler()
		if channel is None or channel[Ipo.PO_QUATW] is None:
			writeStaticFrame(restElr, w)
		else:
			keyframes = []
			curve = channel[Ipo.PO_QUATW]
			ptmaxindex = len(curve.bezierPoints) - 1
			ptindex = 0
			for kf in curve.bezierPoints:
				armObj.evaluatePose(int(kf.pt[0]))
				bonePoseMat = pose.bones[b["name"]].poseMatrix * rotMat
				if i > 0:
					parentPoseMat = (pose.bones[b["name"]].parent.poseMatrix * rotMat).invert()
				else:
					parentPoseMat = Matrix().resize4x4().identity()
				poseRot = (bonePoseMat * parentPoseMat).toEuler(restElr)
				# TODO: fix tangent stuff
				if curve.interpolation != Blender.IpoCurve.InterpTypes.BEZIER:
					if ptindex < ptmaxindex:
						nextpt = curve.bezierPoints[ptindex + 1].pt
						tangent = (nextpt[1] - kf.pt[1]) / (nextpt[0] - kf.pt[0])
					else:
						tangent = 0.0
				else:
					tangent = 0.0
				keyframes.append( { "num": int(kf.pt[0] - 1), "val": poseRot, "tan": tangent } )
				ptindex += 1
			writeKeyframes(keyframes, w)
			
		# translation
		if channel is not None:
			curve = channel[Ipo.PO_LOCX] or channel[Ipo.PO_LOCY] or channel[Ipo.PO_LOCZ]
		else:
			curve = None
		if curve is None:
			restLoc = restMat.translationPart() * SCALEFACTOR
			writeStaticFrame(restLoc, w)
		else:
			keyframes = []
			ptmaxindex = len(curve.bezierPoints) - 1
			ptindex = 0
			for kf in curve.bezierPoints:
				armObj.evaluatePose(int(kf.pt[0]))
				bonePoseMat = pose.bones[b["name"]].poseMatrix
				if i > 0:
					parentPoseMat = pose.bones[b["name"]].parent.poseMatrix.invert()
				else:
					parentPoseMat = Matrix().resize4x4().identity()
				poseLoc = (bonePoseMat * parentPoseMat).translationPart() * SCALEFACTOR
				# TODO: fix tangent stuff
				if curve.interpolation != Blender.IpoCurve.InterpTypes.BEZIER:
					if ptindex < ptmaxindex:
						nextpt = curve.bezierPoints[ptindex + 1].pt
						tangent = (nextpt[1] - kf.pt[1]) / (nextpt[0] - kf.pt[0])
					else:
						tangent = 0.0
				else:
					tangent = 0.0
				keyframes.append( { "num": int(kf.pt[0] - 1), "val": poseLoc, "tan": tangent } )
				ptindex += 1
			writeKeyframes(keyframes, w)
		
		i += 1
	
	writeInt(255, w) # footer

def writeBoneAnims(actions, armObj, bonelist, reference, w, r):
	print "Writing bone animations"
	
	if reference is None:
		# not using reference file
		animCount = len(actions)
		writeInt(animCount, w)
		# writing dummy
		if "dummy" in actions:
			dummy = 1
			writeInt(0, w)
			print "\tWriting dummy"
			del actions["dummy"]
		else:
			dummy = 0
		# writing animations
		print "\tAnimations : %d" % (animCount - dummy)
		for action in actions.values():
			writeBoneAnimation(action, armObj, bonelist, w)
	else:
		# using reference file
		# counting animations
		appendCount = 0 # animations to append
		animCount = len(reference) # animations from reference file
		while actions.has_key(animCount + appendCount):
			appendCount += 1
		writeInt(animCount + appendCount, w)
		# writing dummy
		if reference[0]["type"] == "0":
			dummy = 1
			writeInt(0, w)
			print "\tWriting dummy"
		else:
			dummy = 0
		# copy and replace
		replaced = 0
		buf = None
		for refId in xrange(0, animCount):
			if actions.has_key(refId):
				writeBoneAnimation(actions[refId], armObj, bonelist, w)
				replaced += 1
				del actions[refId]
			else:
				r.seek(reference[refId + dummy]["offset"])
				buf = r.read(reference[refId + dummy]["length"])
				w.write(buf)
		del buf
		buf = None
		# append
		for refId in xrange(0, appendCount):
			writeBoneAnimation(actions[animCount + refId], armObj, bonelist, w)
		
		print "\tReplaced : %d" % replaced
		print "\tAdded : %d" % appendCount
		print "\tTotal : %d" % (animCount + appendCount)

def readAnimations(r):
	animations = []
	animationCount = readInt(r)
	for i in xrange(0, animationCount):
		animation = {}
		animation["offset"] = r.tell()
		animation["type"] = readInt(r)
		if (animation["type"] == 0):
			animations.append(animation)
			continue
		r.read(readInt(r) * 4)
		if (animation["type"] == 1): animation["frameCount"] = readInt(r)
		else: animation["frameCount"] = 0
		animation["objects"] = []
		boneCount = readInt(r)
		for j in xrange(0, boneCount):
			bone = {}
			if (animation["type"] == 1): bone["name"] = readString(r)
			bone["num"] = readInt(r)
			bone["SRT"] = []
			SRTSize = (9 if (animation["type"] == 1) else 2)
			for k in xrange(0, SRTSize):
				data = {}
				data["flag"] = r.read(1)
				data["value"] = readSingle(r)
				data["keyFrameCount"] = readInt(r)
				if data["flag"] == "\x01":
					data["keyFrames"] = []
					for l in xrange(0, data["keyFrameCount"]):
						kf = {}
						kf["num"] = int(readSingle(r))
						kf["value"] = readSingle(r)
						kf["tangent"] = readSingle(r)
						data["keyFrames"].append(kf)
				bone["SRT"].append(data)
			animation["objects"].append(bone)
		animation["footer"] = readInt(r)
		animation["length"] = r.tell() - animation["offset"]
		animations.append(animation)
		
	return animations

def writeMeshes(data, w):
	print "Writing meshes"
	bonedict = {}
	morphIndexes = []
	wMatrixes = {}
	meshcount = len(data["meshes"])
	# Writing mesh count
	writeInt(meshcount, w)
	for i, b in enumerate(data["bones"]):
		bonedict[b["name"]] = i
	fileHasBones = True if len(bonedict) > 0 else False
	# Main mesh loop
	for meshInfo in data["meshes"]:
		mesh = bpy.data.objects[meshInfo["objName"]].getData(mesh = True)
		worldMatrix = bpy.data.objects[meshInfo["objName"]].matrixWorld
		wMatrixes[meshInfo["objName"]] = worldMatrix.copy()
		print "\t%s (Material: %s)" % (meshInfo["objName"], data["materials"][meshInfo["matId"]]["texname"])
		# Material
		writeInt(meshInfo["matId"], w)
		vertToBones = {}
		if fileHasBones:
			# Mapping vertex groups to bones
			vGroups = mesh.getVertGroupNames()
			attachedBonesIds = []
			currentGrpId = 0
			for vg in vGroups:
				if bonedict.has_key(vg):
					verts = mesh.getVertsFromGroup(vg, 1)
					attachedBonesIds.append(bonedict[vg])
					# Associate vertices ids with bones ids and weights
					for tup in verts:
						if not vertToBones.has_key(tup[0]):
							vertToBones[tup[0]] = [ [], [] ]
						vertToBones[tup[0]][0].append(currentGrpId)
						vertToBones[tup[0]][1].append(tup[1])
					currentGrpId += 1
			attachcount = len(attachedBonesIds)
			# Writing attached bones count
			if attachcount > 0:
				writeInt(attachcount, w)
				for bid in attachedBonesIds:
					writeInt(bid, w)
			else:
				# Attach to root bone
				writeInt(1, w)
				writeInt(0, w)
				for v in mesh.verts:
					vertToBones[v.index] = [ [0], [1] ]
		else:
			# Reference ANZ has no bones (which is impossible?)
			writeInt(0, w)
		# Making native faces
		vertMap = {}
		vertUniqueList = []
		vertIndexList = []
		noBonesVerts = []
		vCount = 0
		for face in mesh.faces:
			vi = 0
			for vert in face.verts:
				v = AnzVertex()
				wV = vert.co * worldMatrix
				v.x = wV.x
				v.y = wV.y
				v.z = wV.z
				v.nx = vert.no.x
				v.ny = vert.no.y
				v.nz = vert.no.z
				v.tu = face.uv[vi].x
				v.tv = 1 - face.uv[vi].y
				v.m = vert.index # for morph targets
				if vertToBones.has_key(vert.index):
					v.bones = vertToBones[vert.index][0]
					v.weights = vertToBones[vert.index][1]
				if vertMap.has_key(v):
					vertUniqueList.append(vertMap[v])
				else:
					newIndex = vCount
					vertUniqueList.append(newIndex)
					vertMap[v] = newIndex
				vertIndexList.append(v)
				vi += 1
				vCount += 1
		# Dealing with two sided faces
		dVerts = []
		dIndexes = []
		for face in mesh.faces:
			if (face.mode & Blender.Mesh.FaceModes.TWOSIDE) == Blender.Mesh.FaceModes.TWOSIDE:
				vi = 0
				for vert in face.verts:
					v = AnzVertex()
					wV = vert.co * worldMatrix
					v.x = wV.x
					v.y = wV.y
					v.z = wV.z
					v.nx = - vert.no.x
					v.ny = - vert.no.y
					v.nz = - vert.no.z
					v.tu = face.uv[vi].x
					v.tv = 1 - face.uv[vi].y
					v.m = vert.index # for morph targets
					dVerts.append(v)
					vi += 1
		if len(dVerts) > 0:
			tmpList = []
			for i in xrange(len(dVerts) - 3, -1, -3):
				tmpList.extend([ dVerts[i], dVerts[i + 2], dVerts[i + 1] ])
			vertIndexList.extend(tmpList)
			for v in tmpList:
				if vertToBones.has_key(vert.index):
					v.bones = vertToBones[vert.index][0]
					v.weights = vertToBones[vert.index][1]
				if vertMap.has_key(v):
					dIndexes.append(vertMap[v])
				else:
					newIndex = vCount
					dIndexes.append(newIndex)
					vertMap[v] = newIndex
				vCount += 1
			vertUniqueList.extend(dIndexes)
		# Stripifying mesh
		NvTriStrip = subprocess.Popen([NvTriStripPath], 
			stdin = subprocess.PIPE, stdout = subprocess.PIPE)
		for vi in vertUniqueList:
			NvTriStrip.stdin.write(str(vi) + " ")
		NvTriStrip.stdin.write("-1\n")
		stripcount = int(NvTriStrip.stdout.readline())
		if stripcount < 1:
			raise Exception("NvTriStrip returned 0 strips. Aborting")
		nativelist = []
		striptype = int(NvTriStrip.stdout.readline())
		nativelength = int(NvTriStrip.stdout.readline())
		nativelist.extend(map(int, NvTriStrip.stdout.readline().split()))
		# Flipping normals
		nativelist.insert(0, nativelist[0])
		nativelength += 1
		# Write native vertices
		writeInt(nativelength, w)
		for vi in nativelist:
			v = vertIndexList[vi]
			# axis and scale
			writeSingle(v.x * SCALEFACTOR, w)
			writeSingle(v.z * SCALEFACTOR, w)
			writeSingle(-v.y * SCALEFACTOR, w)
			# vector normals
			writeSingle(v.nx, w)
			writeSingle(v.nz, w)
			writeSingle(-v.ny, w)
			# uvs
			writeSingle(v.tu, w)
			writeSingle(v.tv, w)
			# bones and weights
			bl = len(v.bones)
			wl = len(v.weights)
			for k in xrange (0, 4):
				if k < bl:
					writeInt(v.bones[k], w)
				else:
					writeInt(-1, w)
			for k in xrange (0, 4):
				if k < wl:
					# QUALITY CONTROL
					writeInt(Decimal("%.2f" % v.weights[k]) * 100, w)
				else:
					writeInt(0, w)
			# ?
			writeInt(0, w)
		# footer
		writeByte(0, w)
		for i in xrange(0, 4):
			writeByte(0xff, w)
		if data["morphchannels"] > 0:
			morphIndexes.append( (meshInfo["objName"], nativelist, vertIndexList) )
	# meshes end
	for i in xrange(0, 12):
		writeByte(0, w)
	# Writing morph targets
	if data["morphchannels"] < 1: return
	print "Writing morph targets"
	totalVerts = 0
	for baseInfo in morphIndexes:
		totalVerts += len(baseInfo[1])
	for cid in xrange(0, data["morphchannels"]):
		index = 0
		writeInt(totalVerts, w)
		for baseInfo in morphIndexes:
			try:
				morphObjName = "m-%d-%s" % (cid, baseInfo[0])
				targetVerts = bpy.data.objects[morphObjName].getData(mesh = True).verts
			except Exception:
				raise Exception("Can't find morph object %s" % morphObjName)
			print "\t" + morphObjName
			for vi in baseInfo[1]:
				v = baseInfo[2][vi]
				wTV = targetVerts[v.m].co * wMatrixes[baseInfo[0]]
				writeInt(index, w)
				writeSingle((wTV.x - v.x) * SCALEFACTOR, w)
				writeSingle((wTV.z - v.z) * SCALEFACTOR, w)
				writeSingle((v.y - wTV.y) * SCALEFACTOR, w)
				index += 1

#----------------
# GUI
#----------------
refAnz = Blender.Draw.Create("")
saveAs = Blender.Draw.Create("")
SCALEFACTOR = Blender.Draw.Create(10.0)
ONLYRENDERABLE = Blender.Draw.Create(1)
MATANIM = Blender.Draw.Create(0)
EXPARM = Blender.Draw.Create(0)
BONEANIM = Blender.Draw.Create(0)
USEREF = Blender.Draw.Create(0)

def getRefAnz(path):
	global refAnz
	refAnz.val = path
	Blender.Draw.Redraw(1)

def getSaveAs(path):
	global saveAs
	if not BPyMessages.Warning_SaveOver(path):
		return
	if not path.lower().endswith(".anz"):
		path += ".anz"
	saveAs.val = path
	Blender.Draw.Redraw(1)

def guiEvent(event, value):	
	if event == Blender.Draw.ESCKEY:
		Blender.Draw.Exit()
		return

def guiButton(event):
	global refAnz, saveAs
	if event == 11:
		Blender.Window.FileSelector(getSaveAs, "Save", Blender.sys.makename(ext = ".anz"))
	elif event == 5:
		if MATANIM.val == 1:
			BONEANIM.val = 0
			guiButton(7)
		else:
			USEREF.val = 0
	elif event == 7:
		if BONEANIM.val == 1:
			MATANIM.val = 0
		else:
			USEREF.val = 0
	elif event == 8:
		if USEREF.val == 1:
			BONEANIM.val = 1
			guiButton(7)
	elif event == 21:
		Blender.Window.FileSelector(getRefAnz, "Select")
	elif event == 666:
		if saveAs.val == "" or refAnz.val == "":
			return
		exportAnz()
		Blender.Draw.Exit()
	elif event == 999:
		Blender.Draw.Exit()
	
	Blender.Draw.Redraw(1)

def drawGui():
	global refAnz, saveAs, SCALEFACTOR, ONLYRENDERABLE, MATANIM, EXPARM, BONEANIM, \
	USEREF
	
	glClearColor(0.55, 0.57, 0.67, 1)
	glClear(BGL.GL_COLOR_BUFFER_BIT)
	
	glColor3f(0.70, 0.71, 0.85)
	glRasterPos2i(35, 40)
	Blender.Draw.Text("KISS ANZ Exporter " + __version__, "large")
	glRasterPos2i(35, 25)
	Blender.Draw.Text(__url__, "small")
	
	yD = 80
	
	saveAs = Blender.Draw.String("Save as: ", 1, 35, 225 + yD, 285, 18, saveAs.val, 255)
	Blender.Draw.PushButton("Browse", 11, 330, 225 + yD, 55, 18, "")
	
	refAnz = Blender.Draw.String("Reference ANZ: ", 2, 35, 200 + yD, 285, 18, refAnz.val, 255)
	Blender.Draw.PushButton("Browse", 21, 330, 200 + yD, 55, 18, "")
	
	glColor3f(0.16, 0.168, 0.2)
	
	SCALEFACTOR = Blender.Draw.Number("Scale:",
		3, 35, 160 + yD, 150, 18, SCALEFACTOR.val, 0, 100)
	glRasterPos2i(195, 165 + yD)
	Blender.Draw.Text("Scale the model by SCALEFACTOR")
	
	ONLYRENDERABLE = Blender.Draw.Toggle("Only renderable",
		4, 35, 135 + yD, 150, 18, ONLYRENDERABLE.val)
	glRasterPos2i(195, 140 + yD)
	Blender.Draw.Text("Only export renderable objects")
	
	EXPARM = Blender.Draw.Toggle("Armature",
		6, 35, 110 + yD, 150, 18, EXPARM.val)
	glRasterPos2i(195, 115 + yD)
	Blender.Draw.Text("Export bones")
	
	MATANIM = Blender.Draw.Toggle("Texture animation",
		5, 35, 85 + yD, 150, 18, MATANIM.val)
	glRasterPos2i(195, 90 + yD)
	Blender.Draw.Text("Export UV offset animation")
	
	BONEANIM = Blender.Draw.Toggle("Bone animation",
		7, 35, 60 + yD, 150, 18, BONEANIM.val)
	glRasterPos2i(195, 65 + yD)
	Blender.Draw.Text("Export bone animation (experimental)")
	
	USEREF = Blender.Draw.Toggle("Use reference",
		8, 95, 35 + yD, 90, 18, USEREF.val)
	glRasterPos2i(195, 40 + yD)
	Blender.Draw.Text("Copy, add and replace animations from the reference file")
	
	Blender.Draw.PushButton("Export", 666, 35, 70, 75, 30, "")
	Blender.Draw.PushButton("Cancel", 999, 120, 70, 75, 30, "")

#----------------
# Main
#----------------
def exportAnz():
	global refAnz, saveAs, SCALEFACTOR, ONLYRENDERABLE, MATANIM, EXPARM, BONEANIM, \
	USEREF
	
	refAnz = refAnz.val
	saveAs = saveAs.val
	SCALEFACTOR = 1.0 if SCALEFACTOR.val == 0 else SCALEFACTOR.val
	ONLYRENDERABLE = ONLYRENDERABLE.val
	MATANIM = MATANIM.val
	EXPARM = EXPARM.val
	BONEANIM = BONEANIM.val
	USEREF = USEREF.val
	
	if refAnz != "" and not Blender.sys.exists(refAnz):
		raise Exception("Reference ANZ file does not exist: %s" % refAnz)
	
	if not saveAs.lower().endswith(".anz"):
		saveAs += ".anz"
	
	filename = Blender.sys.basename(saveAs)
	
	print "======= EXPORTING ANZ ======="
	
	inEditMode = Blender.Window.EditMode()
	if inEditMode: Blender.Window.EditMode(0)
	
	STARTTIME = Blender.sys.time()
	
	data = prepareScene()
	
	with open(saveAs, "wb") as w:
		# Header
		writeString("anz2blend %s %s" % (__version__, filename), w)
		# Scale
		writeSingle(1.0, w)
		
		writeMaterials(data, w)
		writeTextures(data, w)
		
		# reference (useless in some cases)
		with open(refAnz, "rb") as r:
			offsets = {}
			readString(r) # header
			readSingle(r) # scale
			readTextures(r)
			offsets["bones"] = r.tell()
			data["bones"] = readBones(r)
			offsets["animations"] = r.tell()
			
			buf = None
			
			if EXPARM:
				data["bones"] = writeBones(data["armature"], w)
			else:
				r.seek(offsets["bones"])
				buf = r.read(offsets["animations"] - offsets["bones"])
				print "Copying armature"
				w.write(buf)
			
			if MATANIM or BONEANIM:
				if USEREF:
					# only replace specific animations
					refAnims = readAnimations(r)
				else:
					refAnims = None
				if MATANIM:
					writeMatAnims(data["matAnims"], w)
				else:
					writeBoneAnims(data["actions"], data["armature"],
						data["bones"], refAnims, w, r)
				del refAnims
				refAnims = None
			else:
				readAnimations(r)
				offsets["meshes"] = r.tell()
				r.seek(offsets["animations"])
				buf = r.read(offsets["meshes"] - offsets["animations"])
				print "Copying animations"
				w.write(buf)
			
			del buf
			buf = None
		
		writeMeshes(data, w)
	
	if inEditMode: Blender.Window.EditMode(1)
	
	bpy.data.scenes.active.getRenderingContext().currentFrame(1)
	
	print "== Done, took %f seconds ==" % (Blender.sys.time() - STARTTIME)

#----------------
# Here it comes
#----------------
Blender.Draw.Register(drawGui, guiEvent, guiButton)
